#include <mpi.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define DEBUG 1

void printMatriz(int *m, int linhas, int colunas)
{
	printf("------------------\n");
	int *m_temp = m;
	int i, j;
	for(i = 0; i < linhas; i++)
	{
		for(j = 0; j < colunas; j++)
		{
			printf("%d;", *(m_temp));
			m_temp++;
		}
		printf("\n");
	}
	printf("------------------\n");
}

int prefix()
{
	srand(time(NULL));
	int px = (rand() % 1000) + 1;
	
	return px;
}

void gravaMatriz(int *m, int linhas, int colunas, int px, int t)
{
	FILE *arq;
	char nome[32];
	sprintf(nome, "%d_resultado_t%d.txt", px, (t+1));
	
	arq = fopen(nome, "a+");
	
	int *m_temp = m;
	int i, j;
	for(i = 0; i < linhas; i++)
	{
		for(j = 0; j < colunas; j++)
		{
			fprintf(arq, "%d", *(m_temp));
			
			if(j < (colunas - 1))
			{
				fprintf(arq, ";");
			}
			m_temp++;
		}
		fprintf(arq, "\n");
	}
	
	fclose(arq);
}

void zeraMatriz(int *m, int linhas, int colunas){
	int i, j;
	for(i = 0; i < linhas; i++)
	{
		for(j = 0; j < colunas; j++)
		{
			*m = 0;
			m++;
		}
	}
}

void copiaMatriz(int *m, int linhas, int colunas, int **nm){
	int i, j;
	int *nmt;
	*nm = (int *)malloc(linhas*colunas*sizeof(int));
	nmt = *nm;
	
	for(i = 0; i < linhas; i++)
	{
		for(j = 0; j < colunas; j++)
		{
			*nmt = *m;
			nmt++;
			m++;
		}
	}
}

int formula(int valor, int a, int b, int c, int d)
{
	int resultado = valor + ( 3 * ( (a + b + c + d) - (4 * valor) ) );
	
	return resultado;
}

int mGet(int *m, int l, int c, int linhas, int colunas)
{
	int *m_temp = m;
	//nao estou usando linhas
	return *( m_temp + c + (l * colunas) );
}

void mSet(int *m, int l, int c, int linhas, int colunas, int valor)
{
	int *m_temp = m;
	//nao estou usando linhas
	*( m_temp + c + (l * colunas) ) = valor;
}

void calculaTemperatura(int *matriz_base, int **matriz_nova, int linhas, int colunas)
{
	int i, j, valor, a, b, c, d, resultado;
	int *matriz_base_p = matriz_base;
	
	zeraMatriz(*(matriz_nova), linhas, colunas);
	
	//Copio as "bordas"
	for(i = 0; i < linhas; i++)
	{
		valor = mGet(matriz_base_p, i, 0, linhas, colunas);
		mSet(*(matriz_nova), i, 0, linhas, colunas, valor);
		
		valor = mGet(matriz_base_p, i, colunas-1, linhas, colunas);
		mSet(*(matriz_nova), i, colunas-1, linhas, colunas, valor);
	}
	
	for(j = 0; j < colunas; j++)
	{
		valor = mGet(matriz_base_p, 0, j, linhas, colunas);
		mSet(*(matriz_nova), 0, j, linhas, colunas, valor);
		
		valor = mGet(matriz_base_p, linhas-1, j, linhas, colunas);
		mSet(*(matriz_nova), linhas-1, j, linhas, colunas, valor);
	}
	//--
	
	for(i = 1; i < linhas-1; i++)
	{
		for(j = 1; j < colunas-1; j++)
		{
			valor = mGet(matriz_base_p, i, j, linhas, colunas);
			a = mGet(matriz_base_p, i-1, j, linhas, colunas);
			b = mGet(matriz_base_p, i, j+1, linhas, colunas);
			c = mGet(matriz_base_p, i+1, j, linhas, colunas);
			d = mGet(matriz_base_p, i, j-1, linhas, colunas);
			
			resultado = formula(valor, a, b, c, d);
			mSet(*(matriz_nova), i, j, linhas, colunas, resultado);
			
			if(DEBUG >= 5)
			{
				printf("Para o valor: %d - a: %d, b: %d, c: %d, d: %d = %d\n", valor, a, b, c, d, resultado);
			}
		}
	}
}

void copyLinhaFirst(int *buffer, int colunas, int **first)
{
	int i;
	int *pb = buffer;
	int *f = *first;
	
	for(i = 0; i < colunas; i++)
	{
		*f = *pb;
		pb++;
		f++;
	}
}

void copyLinhaLast(int *buffer, int linhas, int colunas, int **first)
{
	int i;
	int *pb = buffer;
	int *f = *first;
	
	pb = pb + (linhas * colunas) - colunas;
	
	for(i = 0; i < colunas; i++)
	{
		*f = *pb;
		pb++;
		f++;
	}
}

int main(int argc, char *argv[])
{	
	//Matriz
	int linhas, colunas, px;	
	px = prefix();
	
	//MPI
	int mpierr, rank, size, first, last, tag;
	
	int BUFFER_SIZE;
	
	int BUFFER_LINHAS;
	int BUFFER_COLUNAS;
	
	int *BUFFER_VETOR_SEND = NULL;
	
	int *BUFFER_VETOR_RECEIVE;
	
	int NODE_LINHAS;
	int NODE_COLUNAS;
	int *NODE_BUFFER;
	int *NODE_BUFFER_CALCULADO;
	
	int *BUFFER_LINHA_FIRST;
	int *BUFFER_LINHA_LAST;
	
	MPI_Status status;
	
	
	tag = 127;
	mpierr = MPI_Init(&argc, &argv);
	
	if (mpierr < 0)
	{
		printf ("Nao foi possivel inicializar o processo MPI!\n");
		return 0;
	}
	
	//Pego o rank
	MPI_Comm_rank(MPI_COMM_WORLD, &rank);
	MPI_Comm_size(MPI_COMM_WORLD, &size);
	
	first = 0;
	last = size - 1;
	
	//auxiliares
	int i, j, t, tempos = 2;
		
	if(rank == 0){
	
		//FILE
		char file_buffer[1024];
		FILE *fp;
		
		//Abro o arquivo
		fp = fopen("dimensoes_e_condicoes.txt", "r");
		
		//Leio as dimensoes
		int fsflag = fscanf(fp, "%d;%d", &linhas, &colunas);
		if(fsflag <= 0)
		{
			printf("Problema ao ler a primeira linha do arquivo");
			return 0;
		}
		printf("Dimensao com  %d linhas por %d colunas \n", linhas, colunas);
		
		if( (linhas % size ) != 0)
		{
			printf("O Scatter exige que a matriz seja particionada igualmente\n");
			printf("logo o numero de processos deve ser um multiplo das linhas.\n\n");
			printf("O programa sera abortado!\n");
			exit(1);
		}
		
		
		printf("Os arquivos serao impressos com o prefixo %d\n", px);
		
		int *matriz = (int *) malloc(linhas*colunas*sizeof(int));
		int *m = matriz;
		
		zeraMatriz(m, linhas, colunas);
		
		m = matriz;
		
		//preparo para o parse dos valores a serem preenchidos na matrizd
		char delims[] = ";";
		char *result = NULL;
		i = 0; //linha
		while( fscanf(fp, "%s", file_buffer) > 0 )
		{
			j = 0;//coluna
			result = strtok(file_buffer, delims);
			while( result != NULL )
			{
				int valor = atoi(result);
				*m = valor;
				m++;
				result = strtok( NULL, delims );
				j++;
			}
			i++;
		}
		
		fclose(fp);
		
		m = matriz;
		copiaMatriz(m, linhas, colunas, &BUFFER_VETOR_SEND);
		
		BUFFER_LINHAS = linhas / size;
		BUFFER_COLUNAS = colunas;
		BUFFER_SIZE = BUFFER_LINHAS * BUFFER_COLUNAS;
	}
		
	//Distribuo o tamanho do buffer pra todo mundo
	MPI_Bcast(&BUFFER_SIZE, 1, MPI_INT, 0, MPI_COMM_WORLD);
	MPI_Bcast(&BUFFER_LINHAS, 1, MPI_INT, 0, MPI_COMM_WORLD);
	MPI_Bcast(&BUFFER_COLUNAS, 1, MPI_INT, 0, MPI_COMM_WORLD);
	
	if(DEBUG >= 3)
	{
		printf("DEBUGANDO(%d) BUFFER_SIZE: %d\n", rank, BUFFER_SIZE);
		printf("DEBUGANDO(%d) BUFFER_LINHAS: %d\n", rank, BUFFER_LINHAS);
		printf("DEBUGANDO(%d) BUFFER_COLUNAS: %d\n\n", rank, BUFFER_COLUNAS);
	}
	
	BUFFER_VETOR_RECEIVE = (int *)malloc(BUFFER_SIZE*sizeof(int));
	
	//começo a criar um novo buffer para manipular matrizes diferentes
	if(rank == first || rank == last)
	{
		NODE_LINHAS = BUFFER_LINHAS + 1;
	}
	else
	{
		NODE_LINHAS = BUFFER_LINHAS + 2;
	}
	NODE_COLUNAS = BUFFER_COLUNAS;
	NODE_BUFFER = (int *) malloc(NODE_LINHAS * NODE_COLUNAS * sizeof(int));
	NODE_BUFFER_CALCULADO = (int *) malloc(NODE_LINHAS * NODE_COLUNAS * sizeof(int));
	BUFFER_LINHA_FIRST = (int *) malloc(NODE_COLUNAS * sizeof(int));
	BUFFER_LINHA_LAST = (int *) malloc(NODE_COLUNAS * sizeof(int));
	
	int *bvr_temp = BUFFER_VETOR_RECEIVE;
	
	int *nb_temp = NODE_BUFFER;
	int *nbc_temp = NODE_BUFFER_CALCULADO;
	int *blf_temp = BUFFER_LINHA_FIRST;
	int *bll_temp = BUFFER_LINHA_LAST;
	
	for(t = 0; t < tempos; t++)
	{
		BUFFER_VETOR_RECEIVE = bvr_temp;
		
		NODE_BUFFER = nb_temp;
		NODE_BUFFER_CALCULADO = nbc_temp;
		BUFFER_LINHA_FIRST = blf_temp;
		BUFFER_LINHA_LAST = bll_temp;
	
		//Distribuo cada um a sua parte
		MPI_Scatter(BUFFER_VETOR_SEND, BUFFER_SIZE, MPI_INT, BUFFER_VETOR_RECEIVE , BUFFER_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
		
		//## ENVIO PARA OS ~~IRMAOS~~ ##
		//Todos exceto ultimo
		if(rank < last)
		{
			copyLinhaLast(BUFFER_VETOR_RECEIVE, BUFFER_LINHAS, BUFFER_COLUNAS, &BUFFER_LINHA_LAST);
			MPI_Send(BUFFER_LINHA_LAST, NODE_COLUNAS, MPI_INT, ( rank + 1 ), tag, MPI_COMM_WORLD);
		}
		
		//Todos exceto primeiro
		if(rank > first)
		{
			copyLinhaFirst(BUFFER_VETOR_RECEIVE, BUFFER_COLUNAS, &BUFFER_LINHA_FIRST);
			MPI_Send(BUFFER_LINHA_FIRST, NODE_COLUNAS, MPI_INT, ( rank - 1 ), tag, MPI_COMM_WORLD);
		}
		
		//## RECEBIMENTO ##
		if(rank < last)
		{
			MPI_Recv(BUFFER_LINHA_LAST, NODE_COLUNAS, MPI_INT, ( rank + 1), tag, MPI_COMM_WORLD, &status);
		}
		
		if(rank > first)
		{
			MPI_Recv(BUFFER_LINHA_FIRST, NODE_COLUNAS, MPI_INT, ( rank - 1), tag, MPI_COMM_WORLD, &status);
		}
		
		//## MONTO O NODE_BUFFER ##
		if(rank > first)
		{
			for(i = 0; i < NODE_COLUNAS; i++)
			{
				*NODE_BUFFER = *BUFFER_LINHA_FIRST;
				NODE_BUFFER++;
				BUFFER_LINHA_FIRST++;
			}
		}
		
		for(i = 0; i < BUFFER_LINHAS; i++)
		{
			for(j = 0; j < BUFFER_COLUNAS; j++)
			{
				*NODE_BUFFER = *BUFFER_VETOR_RECEIVE;
				NODE_BUFFER++;
				BUFFER_VETOR_RECEIVE++;
			}
		}
		
		if(rank < last)
		{
			for(i = 0; i < NODE_COLUNAS; i++)
			{
				*NODE_BUFFER = *BUFFER_LINHA_LAST;
				NODE_BUFFER++;
				BUFFER_LINHA_LAST++;
			}
		}
		
		NODE_BUFFER = nb_temp;
		NODE_BUFFER_CALCULADO = nbc_temp;
		calculaTemperatura(NODE_BUFFER, &NODE_BUFFER_CALCULADO, NODE_LINHAS, NODE_COLUNAS);
		
		//RECRIO O VETOR RECEIVE
		BUFFER_VETOR_RECEIVE = bvr_temp;
		NODE_BUFFER_CALCULADO = nbc_temp;
		
		if(rank > first)
		{
			NODE_BUFFER_CALCULADO = NODE_BUFFER_CALCULADO + NODE_COLUNAS;
		}
		
		for(i = 0; i < BUFFER_LINHAS; i++)
		{
			for(j = 0; j < BUFFER_COLUNAS; j++)
			{
				*BUFFER_VETOR_RECEIVE = *NODE_BUFFER_CALCULADO;
				BUFFER_VETOR_RECEIVE++;
				NODE_BUFFER_CALCULADO++;
			}
		}
		
		BUFFER_VETOR_RECEIVE = bvr_temp;
		MPI_Gather(BUFFER_VETOR_RECEIVE, BUFFER_SIZE, MPI_INT, BUFFER_VETOR_SEND, BUFFER_SIZE, MPI_INT, 0, MPI_COMM_WORLD);
		
		//
		if(rank == 0)
		{
			//printMatriz(BUFFER_VETOR_SEND, linhas, colunas);
			gravaMatriz(BUFFER_VETOR_SEND, linhas, colunas, px, t);
		}
	}
	
	MPI_Finalize();
		
	return 0;
}
